// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

// Helper methods for using Tasks to implement the APM pattern.
//
// Example usage, wrapping a Task<int>-returning FooAsync method with Begin/EndFoo methods:
//
//     public IAsyncResult BeginFoo(..., AsyncCallback callback, object state) =>
//         TaskToApm.Begin(FooAsync(...), callback, state);
//
//     public int EndFoo(IAsyncResult asyncResult) =>
//         TaskToApm.End<int>(asyncResult);

//
// Xamarin:
// This is a copy of this file: https://github.com/dotnet/runtime/blob/25d316cefafc4c3884f38acf7bdef971d2b9fe4b/src/libraries/Common/src/System/Threading/Tasks/TaskToApm.cs
// We need it to be backwards compatible in .NET 5 for the implementations of Compression.[Begin|End]|[Read|Write].
//

#nullable enable
using System.Diagnostics;

namespace System.Threading.Tasks {
	/// <summary>
	/// Provides support for efficiently using Tasks to implement the APM (Begin/End) pattern.
	/// </summary>
	internal static class TaskToApm {
		/// <summary>
		/// Marshals the Task as an IAsyncResult, using the supplied callback and state
		/// to implement the APM pattern.
		/// </summary>
		/// <param name="task">The Task to be marshaled.</param>
		/// <param name="callback">The callback to be invoked upon completion.</param>
		/// <param name="state">The state to be stored in the IAsyncResult.</param>
		/// <returns>An IAsyncResult to represent the task's asynchronous operation.</returns>
		public static IAsyncResult Begin (Task task, AsyncCallback? callback, object? state) =>
			new TaskAsyncResult (task, state, callback);

		/// <summary>Processes an IAsyncResult returned by Begin.</summary>
		/// <param name="asyncResult">The IAsyncResult to unwrap.</param>
		public static void End (IAsyncResult asyncResult)
		{
			if (asyncResult is TaskAsyncResult twar) {
				twar._task.GetAwaiter ().GetResult ();
				return;
			}

			throw new ArgumentNullException ();
		}

		/// <summary>Processes an IAsyncResult returned by Begin.</summary>
		/// <param name="asyncResult">The IAsyncResult to unwrap.</param>
		public static TResult End<TResult> (IAsyncResult asyncResult)
		{
			if (asyncResult is TaskAsyncResult twar && twar._task is Task<TResult> task) {
				return task.GetAwaiter ().GetResult ();
			}
			throw new ArgumentNullException ();
		}

		/// <summary>Provides a simple IAsyncResult that wraps a Task.</summary>
		/// <remarks>
		/// We could use the Task as the IAsyncResult if the Task's AsyncState is the same as the object state,
		/// but that's very rare, in particular in a situation where someone cares about allocation, and always
		/// using TaskAsyncResult simplifies things and enables additional optimizations.
		/// </remarks>
		internal sealed class TaskAsyncResult : IAsyncResult {
			/// <summary>The wrapped Task.</summary>
			internal readonly Task _task;
			/// <summary>Callback to invoke when the wrapped task completes.</summary>
			private readonly AsyncCallback? _callback;

			/// <summary>Initializes the IAsyncResult with the Task to wrap and the associated object state.</summary>
			/// <param name="task">The Task to wrap.</param>
			/// <param name="state">The new AsyncState value.</param>
			/// <param name="callback">Callback to invoke when the wrapped task completes.</param>
			internal TaskAsyncResult (Task task, object? state, AsyncCallback? callback)
			{
				Debug.Assert (task is not null);
				_task = task;
				AsyncState = state;

				if (task.IsCompleted) {
					// Synchronous completion.  Invoke the callback.  No need to store it.
					CompletedSynchronously = true;
					callback?.Invoke (this);
				} else if (callback is not null) {
					// Asynchronous completion, and we have a callback; schedule it. We use OnCompleted rather than ContinueWith in
					// order to avoid running synchronously if the task has already completed by the time we get here but still run
					// synchronously as part of the task's completion if the task completes after (the more common case).
					_callback = callback;
					_task.ConfigureAwait (continueOnCapturedContext: false)
						 .GetAwaiter ()
						 .OnCompleted (InvokeCallback); // allocates a delegate, but avoids a closure
				}
			}

			/// <summary>Invokes the callback.</summary>
			private void InvokeCallback ()
			{
				Debug.Assert (!CompletedSynchronously);
				Debug.Assert (_callback is not null);
				_callback.Invoke (this);
			}

			/// <summary>Gets a user-defined object that qualifies or contains information about an asynchronous operation.</summary>
			public object? AsyncState { get; }
			/// <summary>Gets a value that indicates whether the asynchronous operation completed synchronously.</summary>
			/// <remarks>This is set lazily based on whether the <see cref="_task"/> has completed by the time this object is created.</remarks>
			public bool CompletedSynchronously { get; }
			/// <summary>Gets a value that indicates whether the asynchronous operation has completed.</summary>
			public bool IsCompleted => _task.IsCompleted;
			/// <summary>Gets a <see cref="WaitHandle"/> that is used to wait for an asynchronous operation to complete.</summary>
			public WaitHandle AsyncWaitHandle => ((IAsyncResult) _task).AsyncWaitHandle;
		}
	}
}
